package ccm.communicate.smctp;

import ccm.common.*;
import ccm.communicate.common.Communicate;
import ccm.communicate.common.NotStartException;
import ccm.communicate.common.SendFailedException;

import javax.swing.*;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Arrays;

import static ccm.common.Utils.timeFormatter;

/**
 * <p>
 * SimpleMultiChannelTransportProtocol <br/>
 * 简易多通道传输协议 <br/>
 * </p>
 * <p>
 * 长度    2/4字节小段序                 <br/>
 * 时间戳  4/8字节小段序                 <br/>
 * 通道    1字节                        <br/>
 * 版本    1字节                        低6位：版本，第6位 短时间戳，第7位短长度<br/>
 * 校验    2字节小段序                   <br/>
 * 包头    3字节       0XFF 0X12 0X17   <br/>
 * 数据 <br/>
 * </p>
 *
 * <p>
 * 发送条件 不在操作&&(100ms未更新||传输效率90%+||达到缓冲区半满) <br/>
 * len>=包头/(1-传输效率) <br/>
 * </p>
 * <p>
 * 把当前通道的ID转移进正在发送区,重新分配缓冲区
 * </p>
 */
public class Smctp extends Communicate implements StatusChangeListener, ReceiveDataListener
{
    public final static    int          CHANNEL_DEBUG       =0;
    public final static    int          CHANNEL_VISUAL_SCOPE=1;
    protected static final int          smctpVersion        =1;
    protected final static int          CHANNEL_MAX         =2;
    /**
     * 接收缓冲
     */
    protected final        int[]        receiveBuf;
    protected              Communicate  next;
    protected              int          nextChannel;
    /**
     * 接收状态
     */
    protected              ReceiveState receiveState;
    /**
     * 接收缓冲起始
     */
    protected              int          receiveBufStart;
    /**
     * 当前接收的通道
     */
    protected              int          dataChannel;
    /**
     * 指定的的分发通道
     */
    protected              int          designatedDataChannel;
    /**
     * 当前接收的长度
     */
    protected              long         dataLength;
    /**
     * 已经接收的长度
     */
    protected              int          dataReceivedLength;
    /**
     * 上一次接收的US时间戳
     */
    protected              long         lastUS;

    public Smctp() throws TooManyListenerException
    {
        this(null,0);
    }


    public Smctp(Communicate nextIn,int nextChannelIn) throws TooManyListenerException
    {
        super(CHANNEL_MAX);
        next       =nextIn;
        nextChannel=nextChannelIn;
        if(next!=null)
        {
            next.addStatusChangeListener(this);
            next.addReceiveDataListener(this,nextChannel);
        }

        receiveBuf           =new int[(3+1+1+4+8+2)];
        designatedDataChannel=CHANNEL_MAX;
        clear();
    }

    /**
     * 清空
     */
    protected void clear()
    {
        receiveState=ReceiveState.RECEIVING_HEAD;
        Arrays.fill(receiveBuf,0);
        receiveBufStart   =0;
        dataChannel       =CHANNEL_MAX;
        dataLength        =0;
        dataReceivedLength=0;
    }

    public void setNext(Communicate nextIn,int nextChannelIn) throws TooManyListenerException
    {
        stop();
        next       =nextIn;
        nextChannel=nextChannelIn;
        next.addStatusChangeListener(this);
        next.addReceiveDataListener(this,nextChannel);
    }

    /**
     * 获取配置用的面板
     *
     * @return 配置用的面板
     */
    public JPanel getConfigJPanel()
    {
        return new SmctpConfigJPanel(this);
    }

    /**
     * 启动
     */
    public void start()
    {
        clear();
        next.start();
    }

    /**
     * 停止
     */
    public void stop()
    {
        if(next!=null)
            next.stop();
        clear();
    }

    /**
     * 检查是否开启
     *
     * @return true表示开启
     * false表示关闭
     */
    public boolean isOpen()
    {
        return next.isOpen();
    }

    /**
     * 发送数据
     *
     * @param data   数据
     * @param sendCh 通道
     * @throws NotStartException   未启动时抛出此异常
     * @throws SendFailedException 发送失败时抛出此异常
     * @implNote 通道未使用
     */
    public void send(byte[] data,int sendCh) throws NotStartException, SendFailedException
    {
        if(designatedDataChannel==CHANNEL_MAX)
        {
            long nowTimeUs=System.currentTimeMillis()*1000;
            final boolean shortTime=nowTimeUs<0XFFFFFFFFL;
            final boolean shortLen=data.length<0XFFFFL;
            final int version=(smctpVersion&0X3F)|(((shortTime?1:0)<<6)&0X40)|(((shortLen?1:0)<<7)&0X80);
            int start=0;
            final int headLen=(3+1+1+(shortLen?2:4)+(shortTime?4:8)+2);
            int[] buf=new int[headLen];
            start       =shortLen?BufferManager.setUint16(buf,start,data.length):BufferManager.setUint32(buf,start,data.length);
            start       =shortTime?BufferManager.setUint32(buf,start,(int)nowTimeUs):BufferManager.setUint64(buf,start,nowTimeUs);            //写入时间戳
            start       =BufferManager.setUint8(buf,start,sendCh);                 //写入通道
            start       =BufferManager.setUint8(buf,start,version);                //写入版本
            start       =BufferManager.setUint16(buf,start,Crc.crc16Modbus(buf,0,start));
            buf[start+0]=0XFF;
            buf[start+1]=0X12;
            buf[start+2]=0X17;
            byte[] buf2=new byte[buf.length];
            for(int i=0;i<buf.length;++i)
                buf2[i]=(byte)buf[i];
            next.send(buf2,nextChannel);
        }
        next.send(data,nextChannel);
    }

    /**
     * 用于保存参数
     *
     * @param out
     */
    public void writeExternal(ObjectOutput out) throws IOException
    {
        out.writeInt(designatedDataChannel);
    }

    /**
     * 用于加载参数
     *
     * @param in
     */
    public void readExternal(ObjectInput in) throws IOException
    {
        designatedDataChannel=in.readInt();
    }

    public int getDesignatedDataChannel()
    {
        return designatedDataChannel;
    }

    public void setDesignatedDataChannel(int designatedDataChannel)
    {
        this.designatedDataChannel=designatedDataChannel;
        statusChange(StatusChangeEvent.CONFIG);
    }

    /**
     * 接收到数据事件
     *
     * @param event
     */
    synchronized public void receiveData(ReceiveDataEvent event)
    {
        // System.out.println(Arrays.toString(event.getData()));
        if(designatedDataChannel!=CHANNEL_MAX)
        {
            callReceiveDataListener(designatedDataChannel,event);
        }
        else
        {
            for(int i=0;i<event.getData().length;)
            {
                switch(receiveState)
                {
                    case RECEIVING_HEAD:
                        receiveBuf[receiveBufStart]=Byte.toUnsignedInt(event.getData()[i]);
                        ++i;
                        receiveBufStart=(receiveBufStart+1)%receiveBuf.length;
                        if(BufferManager.getUint8(receiveBuf,receiveBufStart-3)==0XFF&&BufferManager.getUint8(receiveBuf,receiveBufStart-2)==0x12&&BufferManager.getUint8(receiveBuf,receiveBufStart-1)==0x17)
                        {
                            final int version=BufferManager.getUint8(receiveBuf,receiveBufStart-6);
                            final boolean shortTime=(version&0X40)!=0;
                            final boolean shortLen=(version&0X80)!=0;
                            final int headLen=(3+1+1+(shortLen?2:4)+(shortTime?4:8)+2);
                            if(BufferManager.getUint16(receiveBuf,receiveBufStart-5)==Crc.crc16Modbus(receiveBuf,receiveBufStart-(headLen),headLen-5))
                            {
                                dataReceivedLength=0;
                                long us=shortTime?BufferManager.getUint32(receiveBuf,receiveBufStart-headLen+(shortLen?2:4)):BufferManager.getUint64(receiveBuf,receiveBufStart-headLen+(shortLen?2:4));
                                dataLength =shortLen?BufferManager.getUint16(receiveBuf,receiveBufStart-headLen):BufferManager.getUint32(receiveBuf,receiveBufStart-headLen);
                                dataChannel=BufferManager.getUint8(receiveBuf,receiveBufStart-7);
                                boolean isNew=us+3000000<lastUS;
                                if(dataChannel<CHANNEL_MAX&&dataLength!=0)
                                {
                                    receiveState=ReceiveState.RECEIVING_DATA;
                                    System.out.printf("SMCTP Receive head%s%s Ch:%2d Si:%6d Ti:%s %s\n",(shortTime?" ST":""),(shortLen?" SL":""),dataChannel,dataLength,timeFormatter(us),(isNew?" new ":""));
                                    if(isNew)
                                        statusChange(StatusChangeEvent.RESTART);
                                    lastUS=us;
                                }
                                else if(dataChannel==0XFF&&dataLength==0)
                                {
                                    System.out.printf("SMCTP Receive heart beat%s%s Ti:%s %s\n",(shortTime?" ST":""),(shortLen?" SL":""),timeFormatter(us),(isNew?" new ":""));
                                    if(isNew)
                                        statusChange(StatusChangeEvent.RESTART);
                                    lastUS=us;
                                }
                                else
                                {
                                    System.out.printf("SMCTP channel failed%s%s\n",(shortTime?" ST":""),(shortLen?" SL":""));
                                }
                            }
                            else
                            {
                                System.out.printf("SMCTP CRC failed%s%s %d\n",(shortTime?" ST":""),(shortLen?" SL":""),headLen);
                            }
                        }
                        break;
                    case RECEIVING_DATA:
                        long len=Math.min(event.getData().length-i,dataLength-dataReceivedLength);
                        callReceiveDataListener(dataChannel,new ReceiveDataEvent(this,Arrays.copyOfRange(event.getData(),(int)i,(int)(i+len)),lastUS));
                        dataReceivedLength+=len;
                        i+=len;
                        if(dataReceivedLength>=dataLength)
                        {
                            receiveState=ReceiveState.RECEIVING_HEAD;
                            // System.out.println("Finish      Ch"+dataChannel+" Size:"+dataLength);
                        }
                        break;
                }

            }
        }
    }

    /**
     * 状态变化事件
     *
     * @param event
     */
    public void statusChange(StatusChangeEvent event)
    {
        if(event.getReason()==StatusChangeEvent.START_STOP)
            clear();
        statusChange(event.getReason());
    }

    /**
     * 获取上一包的时间
     *
     * @return 上一包的时间
     */
    public long getLastUS()
    {
        return lastUS;
    }

    protected enum ReceiveState
    {
        RECEIVING_HEAD,RECEIVING_DATA,
    }

}
